//
//  ProximatorAppDelegate.m
//  Proximator
//
//  Created by Lauri Koskela on 31.7.2011.
//  Copyright (C) 2011 Lauri Koskela
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

#import "ProximatorAppDelegate.h"
#import "ProxyMenuItem.h"

#define kNoProxyID @"NO_PROXY"
#define kActiveProxyKey @"activeProxyID"
#define kSelectedNetworkServiceKey @"selectedNetworkService"

@implementation ProximatorAppDelegate

@synthesize window;
@synthesize proxyArrayController = _proxyArrayController;
@synthesize statusMenu = _statusMenu;
@synthesize removeButton = _removeButton;
@synthesize noProxyMenuItem = _noProxyMenuItem;
@synthesize interfacePopover = _interfacePopover;
@synthesize popoverButton = _popoverButton;
@synthesize networkServiceMatrix = _networkServiceMatrix;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Init status menu
    NSStatusBar *bar = [NSStatusBar systemStatusBar];
    statusItem = [bar statusItemWithLength:NSVariableStatusItemLength];
    [statusItem retain];
    [statusItem setHighlightMode:YES];
    [statusItem setImage:[NSImage imageNamed:NSImageNameStatusNone]];
    [statusItem setMenu:_statusMenu];
    
    //Generate radio buttons for popover
    [self generateNetworkServices];
    
    //Set no proxys ID
    [_noProxyMenuItem setID:kNoProxyID];
    
    //Create status menu items
    [self generateMenuItems];
    
    //Find the selected proxy
    NSString *activeProxyID = [[[NSUserDefaultsController sharedUserDefaultsController] values] valueForKey:kActiveProxyKey];
    
    if ([activeProxyID isEqualToString:kNoProxyID] || activeProxyID == nil) {
        [_noProxyMenuItem setState:NSOnState];
        [self setNoProxy];
        [[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:kNoProxyID forKey:kActiveProxyKey];
    } else {
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Proxy"];
        NSArray *proxyArray = [self.managedObjectContext executeFetchRequest:request error:NULL];
        NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
        proxyArray = [proxyArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
        [sort release];
        
        [proxyArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSString *objectID = [obj valueForKey:@"ID"];
            if ([objectID isEqualToString:activeProxyID]) {
                NSMenuItem *menuItem = [_statusMenu itemAtIndex:idx + 1];
                [menuItem setState:NSOnState];
                [self setProxy:obj];
                *stop = YES;
            }
        }];
        [statusItem setImage:[NSImage imageNamed:NSImageNameStatusPartiallyAvailable]];
    }
    
    //Notify when objects change so sorting can be done
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(sortMenuItems) name:NSManagedObjectContextObjectsDidChangeNotification object:__managedObjectContext];
    
    //Prepare array controller for preferences window
    [_proxyArrayController fetch:self];
}

/**
    Returns the directory the application uses to store the Core Data store file. This code uses a directory named "Proximator" in the user's Library/Application Support directory.
 */
- (NSURL *)applicationFilesDirectory {

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *libraryURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
    return [libraryURL URLByAppendingPathComponent:@"Proximator"];
}

/**
    Creates if necessary and returns the managed object model for the application.
 */
- (NSManagedObjectModel *)managedObjectModel {
    if (__managedObjectModel) {
        return __managedObjectModel;
    }
	
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Proximator" withExtension:@"momd"];
    __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];    
    return __managedObjectModel;
}

/**
    Returns the persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.)
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (__persistentStoreCoordinator) {
        return __persistentStoreCoordinator;
    }

    NSManagedObjectModel *mom = [self managedObjectModel];
    if (!mom) {
        NSLog(@"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd));
        return nil;
    }

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
    NSError *error = nil;
    
    NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] error:&error];
        
    if (!properties) {
        BOOL ok = NO;
        if ([error code] == NSFileReadNoSuchFileError) {
            ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error];
        }
        if (!ok) {
            [[NSApplication sharedApplication] presentError:error];
            return nil;
        }
    }
    else {
        if ([[properties objectForKey:NSURLIsDirectoryKey] boolValue] != YES) {
            // Customize and localize this error.
            NSString *failureDescription = [NSString stringWithFormat:@"Expected a folder to store application data, found a file (%@).", [applicationFilesDirectory path]]; 
            
            NSMutableDictionary *dict = [NSMutableDictionary dictionary];
            [dict setValue:failureDescription forKey:NSLocalizedDescriptionKey];
            error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:101 userInfo:dict];
            
            [[NSApplication sharedApplication] presentError:error];
            return nil;
        }
    }
    
    NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@"Proximator.storedata"];
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
        [[NSApplication sharedApplication] presentError:error];
        [__persistentStoreCoordinator release], __persistentStoreCoordinator = nil;
        return nil;
    }

    return __persistentStoreCoordinator;
}

/**
    Returns the managed object context for the application (which is already
    bound to the persistent store coordinator for the application.) 
 */
- (NSManagedObjectContext *)managedObjectContext {
    if (__managedObjectContext) {
        return __managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        [dict setValue:@"Failed to initialize the store" forKey:NSLocalizedDescriptionKey];
        [dict setValue:@"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey];
        NSError *error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
        [[NSApplication sharedApplication] presentError:error];
        return nil;
    }
    __managedObjectContext = [[NSManagedObjectContext alloc] init];
    [__managedObjectContext setPersistentStoreCoordinator:coordinator];

    return __managedObjectContext;
}

/**
    Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application.
 */
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
    return [[self managedObjectContext] undoManager];
}

/**
    Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user.
 */
- (IBAction)saveAction:(id)sender {
    NSError *error = nil;
    
    if (![[self managedObjectContext] commitEditing]) {
        NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd));
    }

    if (![[self managedObjectContext] save:&error]) {
        [[NSApplication sharedApplication] presentError:error];
    }
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {

    // Save changes in the application's managed object context before the application terminates.

    if (!__managedObjectContext) {
        return NSTerminateNow;
    }

    if (![[self managedObjectContext] commitEditing]) {
        NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd));
        return NSTerminateCancel;
    }

    if (![[self managedObjectContext] hasChanges]) {
        return NSTerminateNow;
    }

    NSError *error = nil;
    if (![[self managedObjectContext] save:&error]) {

        // Customize this code block to include application-specific recovery steps.              
        BOOL result = [sender presentError:error];
        if (result) {
            return NSTerminateCancel;
        }

        NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message");
        NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info");
        NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title");
        NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title");
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:question];
        [alert setInformativeText:info];
        [alert addButtonWithTitle:quitButton];
        [alert addButtonWithTitle:cancelButton];

        NSInteger answer = [alert runModal];
        [alert release];
        alert = nil;
        
        if (answer == NSAlertAlternateReturn) {
            return NSTerminateCancel;
        }
    }

    return NSTerminateNow;
}

- (void)dealloc
{
    [__managedObjectContext release];
    [__persistentStoreCoordinator release];
    [__managedObjectModel release];
    [super dealloc];
}

- (IBAction)showPreferences:(id)sender {
    [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
    [window makeKeyAndOrderFront:self];
}

- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
    [_proxyArrayController fetch:self];
    return YES;
}

- (void)generateMenuItems {
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Proxy"];
    NSArray *proxyArray = [self.managedObjectContext executeFetchRequest:request error:NULL];
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    proxyArray = [proxyArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
    [sort release];
    
    [proxyArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        ProxyMenuItem *menuItem = [[ProxyMenuItem alloc] init];
        [menuItem bind:@"ID" toObject:obj withKeyPath:@"ID" options:nil];
        [menuItem bind:@"title" toObject:obj withKeyPath:@"name" options:nil];
        
        [menuItem setAction:@selector(selectProxy:)];
        [_statusMenu insertItem:menuItem atIndex:idx + 1];
        [menuItem release];
    }];
}

- (IBAction)selectProxy:(id)sender {
    NSString *proxyID = [sender valueForKey:@"ID"];
    
    NSArray *statusMenuItems = [_statusMenu itemArray];
    [statusMenuItems enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [(NSMenuItem *)obj setState:NSOffState];
    }];
    
    if ([proxyID isEqualToString:kNoProxyID]) {
        [_noProxyMenuItem setState:NSOnState];
        [self setNoProxy];
        [[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:kNoProxyID forKey:kActiveProxyKey];
        [statusItem setImage:[NSImage imageNamed:NSImageNameStatusNone]];
    } else {
        if ([_networkServiceMatrix selectedCell] == nil) {
            [_noProxyMenuItem setState:NSOnState];
            
            NSAlert *alert = [[NSAlert alloc] init];
            [alert setMessageText:@"No Network Service Selected"];
            [alert setInformativeText:@"You have not selected a network service. Please go to Proximator preferences and select network service which Proximator will control."];
            [alert setAlertStyle:NSWarningAlertStyle];
            [alert addButtonWithTitle:@"Open Preferences..."];
            [alert addButtonWithTitle:@"Cancel"];
            NSInteger alertResult = [alert runModal];
            if (alertResult == NSAlertFirstButtonReturn) {
                [self showPreferences:self];
                [self showInterfacePopover:_popoverButton];
            } 
            [alert release];
            return;
        }
        
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Proxy"];
        NSArray *proxyArray = [self.managedObjectContext executeFetchRequest:request error:NULL];
        NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
        proxyArray = [proxyArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
        [sort release];
        
        [proxyArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSString *objectID = [obj valueForKey:@"ID"];
            if ([objectID isEqualToString:proxyID]) {
                [[_statusMenu itemAtIndex:idx + 1] setState:NSOnState];
                [_noProxyMenuItem setState:NSOffState];
                [self setProxy:obj];
                [[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:objectID forKey:kActiveProxyKey];
                *stop = YES;
            }
        }];
        [statusItem setImage:[NSImage imageNamed:NSImageNameStatusPartiallyAvailable]];
    }
}

- (void)sortMenuItems {
    NSInteger itemCount = [[_proxyArrayController arrangedObjects] count];
    NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, itemCount)];
    NSArray *itemArray = [[_statusMenu itemArray] objectsAtIndexes:indexes];
    NSSortDescriptor *sort = [[[NSSortDescriptor alloc] initWithKey:@"title" ascending:YES] autorelease];
    itemArray = [itemArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
    
    NSInteger i = 0;
    for (id menuItem in itemArray) {
        [_statusMenu removeItem:menuItem];
        [_statusMenu insertItem:menuItem atIndex:(i + 1)];
        i++;
    }
}

- (void)setProxy:(NSManagedObject*)proxyObject {
    NSString *address = [proxyObject valueForKey:@"address"];
    NSString *port = [proxyObject valueForKey:@"port"];
    
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/usr/sbin/networksetup"];
    
    NSArray *arguments = [NSArray arrayWithObjects:@"-setwebproxy", @"Wi-Fi", address, port, nil];
    [task setArguments:arguments];
    [task launch];
    [task release];
}

- (void)setNoProxy {
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/usr/sbin/networksetup"];
    
    NSArray *arguments = [NSArray arrayWithObjects:@"-setwebproxystate", @"Wi-Fi", @"off", nil];
    [task setArguments:arguments];
    [task launch];
    [task release];
}

- (IBAction)showInterfacePopover:(id)sender {
    if ([_interfacePopover isShown]) {
        [_interfacePopover performClose:sender];
    } else {
        [_interfacePopover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMaxYEdge];
    }
}

- (void)generateNetworkServices {
    //init task and set networksetup path
    NSTask *networksetupTask = [[NSTask alloc] init];
    [networksetupTask setLaunchPath: @"/usr/sbin/networksetup"];
    
    //set arguments for listing all networkservices
    NSArray *arguments = [NSArray arrayWithObjects: @"-listallnetworkservices", nil];
    [networksetupTask setArguments: arguments];
    
    //setup output pipes and stuff
    NSPipe *pipe = [NSPipe pipe];
    [networksetupTask setStandardOutput: pipe];
    NSFileHandle *file = [pipe fileHandleForReading];
    
    //run networksetup
    [networksetupTask launch];
    
    //get output data
    NSData *data;
    data = [file readDataToEndOfFile];
    NSString *rawString = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
    NSString *string = nil;
    
    //if output string starts with this note, remove it
    if ([rawString hasPrefix:@"An asterisk (*) denotes that a network service is disabled."]) {
        string = [rawString substringFromIndex:60];
    } else {
        string = rawString;
    }
    
    //get services to an array
    NSMutableArray *networkServices = [NSMutableArray arrayWithArray:[string componentsSeparatedByString:@"\n"]];
    [networkServices removeObjectAtIndex:[networkServices count] - 1];
    
    [rawString release];
    [networksetupTask release];
    
    for (NSString* serviceName in networkServices) {
        NSButtonCell *cell = [[NSButtonCell alloc] init];
        [cell setButtonType:NSRadioButton];
        [cell setTitle:serviceName];
        
        
        [_networkServiceMatrix addRowWithCells:[NSArray arrayWithObject:cell]];
        [cell release];
    }
    
    [_networkServiceMatrix setAllowsEmptySelection:YES];
    [_networkServiceMatrix sizeToFit];
    
    NSString *selectedService = [[[NSUserDefaultsController sharedUserDefaultsController] values] valueForKey:kSelectedNetworkServiceKey];
    if (selectedService == nil) {
        [_networkServiceMatrix deselectAllCells];
    } else {
        NSArray *serviceRadioButtons = [_networkServiceMatrix cells];
        for (NSButtonCell *cell in serviceRadioButtons) {
            if ([[cell title] isEqualToString:selectedService]) {
                [_networkServiceMatrix selectCell:cell];
                return;
            }
        }
        //If this code is executed, the selected service name (in userdefaults) was not found --> empty selection
        [_networkServiceMatrix deselectAllCells];
    }
    
}
@end
